package de.rs.espresso.algorithms;

import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;

import de.rs.espresso.algorithms.minimumcover.MinimumCover;
import de.rs.espresso.algorithms.minimumcover.model.Matrix;
import de.rs.espresso.algorithms.minimumcover.model.Solution;
import de.rs.espresso.covers.DefaultCover;
import de.rs.espresso.cubes.FreezedCube;
import de.rs.espresso.interfaces.IAlgorithm;
import de.rs.espresso.interfaces.ICover;
import de.rs.espresso.interfaces.ICube;
import de.rs.espresso.interfaces.IGlobalInformation;
import de.rs.espresso.tools.Statistics;

/**
 * This algorithm removes the redundant cubes from the given Onset. The compute
 * method expects the onSet and dcSet. The offSet might be null.
 * 
 * @author Jan
 * 
 */
public class Irredundant implements IAlgorithm {


	
	private ICover origOnSet;
	private ICover origDcSet;

	private ICover essentials;
	private ICover redundant;

	private ICover partiallyRedundant;

	private IGlobalInformation info;
	
	public Irredundant(IGlobalInformation info) {
		this.info = info;
	}
	
	@Override
	public ICover compute(ICover onSet, ICover offSet, ICover dcSet) {
		
		long measurementId = Statistics.startMeasurement("IRREDUNDANT");

		assert (onSet != null && dcSet != null);

		this.origOnSet = onSet;
		this.origDcSet = dcSet;

		determineRedundant();

		determinePartiallyRedundant();

		determineMinimalIrredundant();
		
		Statistics.stopMeasurement(measurementId);

		return essentials;
	}

	private void determineMinimalIrredundant() {
		List<BitSet> rows = determineMatrix();
		
		Solution<ICube> minimumCoverSolution = new MinimumCover<ICube>(
				partiallyRedundant, rows).compute();
		
		essentials.addAll(minimumCoverSolution.toList());

	}

	private void determinePartiallyRedundant() {
		partiallyRedundant = new DefaultCover(info);

		ICover s = origDcSet.clone();

		s.addAll(essentials);

		for (ICube r : redundant) {

			if (!s.isTautology(r))
				partiallyRedundant.add(r);

		}

	}

	private void determineRedundant() {

		essentials = new DefaultCover(info);
		redundant = new DefaultCover(info);

		for (ICube c : origOnSet) {

			ICover clonedOnSet = origOnSet.clone();
			ICover clonedDcSet = origDcSet.clone();

			ICover s = clonedOnSet;

			s.addAll(clonedDcSet);
			s.remove(c);

			if (s.isTautology(c))
				redundant.add(c);
			else
				essentials.add(c);

		}

	}

	private List<BitSet> determineMatrix() {

		List<BitSet> rows = new ArrayList<BitSet>();

		// Er or Fdc
		ICover essentialInklDC = essentials.clone();
		essentialInklDC.addAll(origDcSet.clone());

		// H = E or Rp or Fdc
		ICover H = essentials.clone();
		H.addAll(partiallyRedundant.clone());
		H.addAll(origDcSet.clone());

		for (ICube cube : partiallyRedundant) {

			// Ha = coFactorOf((H-a),a)
			ICover Ha = H.clone();
			Ha.remove(cube);
			Ha.freezeCubes();

			Ha = Ha.getCofactor(cube);

			List<BitSet> bitSets = getMatrixRows(Ha, essentialInklDC, cube);

			rows.addAll(bitSets);

		}

		return rows;

	}

	private List<BitSet> getMatrixRows(ICover Ha, ICover ErOrFdc, ICube aktCube) {

		List<List<ICube>> cubesList = Ha.getTautologyCubes();

		List<BitSet> bitSets = new ArrayList<BitSet>();
		for (List<ICube> cubes : cubesList) {

			List<ICube> unwrappedCubes = unwrapCubes(cubes);

			boolean contained = false;
			for (ICube c : unwrappedCubes)
				if (ErOrFdc.contains(c))
					contained = true;

			if (!contained) {
				BitSet bitSet = new BitSet(partiallyRedundant.size());
				bitSet.set(0, partiallyRedundant.size());

				int j = 0;
				for (ICube c : partiallyRedundant) {

					if (unwrappedCubes.contains(c)||c.equals(aktCube)) {

						bitSet.set(j);

					} else {
						bitSet.clear(j);
					}

					j++;

				}

				bitSets.add(bitSet);
			}
		}

		return bitSets;

	}

	private List<ICube> unwrapCubes(List<ICube> cubes) {
		ArrayList<ICube> unwrappedCubes = new ArrayList<ICube>();
		for (ICube c : cubes) {
			if (c instanceof FreezedCube) {
				unwrappedCubes.add(((FreezedCube) c).getOriginalCube());
			}
		}
		return unwrappedCubes;
	}

}
